/*
 * Ethan Levine
 * 
 * The contents of this file are subject to the GNU General Public
 * License (GPL) v2.  The full contents of this license can be
 * found at Mathma's website, http://www.codeplex.com/mathma/license
 */

package mathma.core.real;

import mathma.core.expr.*;
import java.math.BigDecimal;
/**
 * @since 0.1.0
 * @author Ethan Levine
 */
public class InfiniteReal extends Real {
    
    public static final InfiniteReal POSITIVE_INFINITY = new InfiniteReal(true);
    public static final InfiniteReal NEGATIVE_INFINITY = new InfiniteReal(false);
    
    private boolean isPositive;
    
    private InfiniteReal(boolean isPositive) {
        this.isPositive = isPositive;
    }
    
    public float floatValue() {
        if (isPositive) {
            return Float.POSITIVE_INFINITY;
        } else {
            return Float.NEGATIVE_INFINITY;
        }
    }
    
    public double doubleValue() {
        if (isPositive) {
            return Double.POSITIVE_INFINITY;
        } else {
            return Double.NEGATIVE_INFINITY;
        }
    }
    
    public byte byteValue() {
        throw new UnknownValueException(
                "byte cannot represent infinite numbers.");
    }
    
    public short shortValue() {
        throw new UnknownValueException(
                "short cannot represent infinite numbers.");
    }
    
    public int intValue() {
        throw new UnknownValueException(
                "int cannot represent infinite numbers.");
    }
    
    public long longValue() {
        throw new UnknownValueException(
                "long cannot represent infinite numbers.");
    }
    
    public BigDecimal bigDecimalValue() {
        return new BigDecimal(doubleValue());
    }
    
    public boolean isPositive() {
        return isPositive;
    }
    
    public boolean isNegative() {
        return !isPositive;
    }
    
    public boolean isFinite() {
        return false;
    }
    
    public boolean isInfinite() {
        return true;
    }
    
    public boolean isIndeterminate() {
        return false;
    }
    
    public boolean isInteger() {
        return false;
    }
    
    public boolean isLessThan(Real other) {
        if (other.isIndeterminate()) {
            return false;
        } else if (other.isFinite()) {
            return isNegative();
        } else if (other.isInfinite()) {
            if (other.isPositive()) {
                return isNegative();
            } else {
                return false;
            }
        } else {
            return false;
        }
    }
    
    public boolean isGreaterThan(Real other) {
        if (other.isIndeterminate()) {
            return false;
        } else if (other.isFinite()) {
            return isPositive();
        } else if (other.isInfinite()) {
            if (other.isNegative()) {
                return isPositive();
            } else {
                return false;
            }
        } else {
            return false;
        }
    }
    
    @Override
    public boolean equals(Object other) {
        return false;
    }
    
    public Real negate() {
        if (isPositive) {
            return NEGATIVE_INFINITY;
        } else {
            return POSITIVE_INFINITY;
        }
    }
    
    public Real absoluteValue() {
        return POSITIVE_INFINITY;
    }
    
    public Real floor() {
        return this;
    }
    
    public Real ceiling() {
        return this;
    }
    
    public Real truncate() {
        return this;
    }

    public Real round() {
        return this;
    }
    
    public Real add(Real operand) {
        if (operand.isInfinite()) {
            if (operand.isPositive()) {
                if (isPositive) {
                    return InfiniteReal.POSITIVE_INFINITY;
                } else {
                    return IndeterminateReal.INF_MINUS_INF;
                }
            } else {
                if (isPositive) {
                    return IndeterminateReal.INF_MINUS_INF;
                } else {
                    return InfiniteReal.NEGATIVE_INFINITY;
                }
            }
        } else if (operand.isIndeterminate()) {
            return IndeterminateReal.INDETERMINATE_OPERATOR;
        } else {
            return this;
        }
    }
    
    public Real subtract(Real operand) {
        if (operand.isInfinite()) {
            if (operand.isPositive()) {
                if (isPositive) {
                    return IndeterminateReal.INF_MINUS_INF;
                } else {
                    return InfiniteReal.NEGATIVE_INFINITY;
                }
            } else {
                if (isPositive) {
                    return InfiniteReal.POSITIVE_INFINITY;
                } else {
                    return IndeterminateReal.INF_MINUS_INF;
                }
            }
        } else if (operand.isIndeterminate()) {
            return IndeterminateReal.INDETERMINATE_OPERATOR;
        } else {
            return this;
        }
    }
    
    public Real multiply(Real operand) {
        if (operand.isIndeterminate()) {
            return IndeterminateReal.INDETERMINATE_OPERATOR;
        } else if (operand.isPositive()) {
            return this;
        } else if (operand.isNegative()) {
            return this.negate();
        } else {
            return Real.ZERO;
        }
    }
    
    public Real divideBy(Real denominator) {
        if (denominator.isInfinite()) {
            return IndeterminateReal.INF_OVER_INF;
        } else if (denominator.isIndeterminate()) {
            return IndeterminateReal.INDETERMINATE_OPERATOR;
        } else if (denominator.isNegative()) {
            return this.negate();
        } else {
            return this;
        }
    }
    
    public Real modulo(Real modulus) {
        if (modulus.isIndeterminate()) {
            return IndeterminateReal.INDETERMINATE_OPERATOR;
        } else {
            return IndeterminateReal.INDETERMINATE_RANGE;
        }
    }
    
    public Real sine() {
        return IndeterminateReal.INDETERMINATE_RANGE;
    }
    
    public Real cosine() {
        return IndeterminateReal.INDETERMINATE_RANGE;
    }
    
    public Real tangent() {
        return IndeterminateReal.INDETERMINATE_RANGE;
    }
    
    public Real secant() {
        return IndeterminateReal.INDETERMINATE_RANGE;
    }
    
    public Real cosecant() {
        return IndeterminateReal.INDETERMINATE_RANGE;
    }
    
    public Real cotangent() {
        return IndeterminateReal.INDETERMINATE_RANGE;
    }
    
    public Real invSine() {
        return IndeterminateReal.OUT_OF_DOMAIN;
    }
    
    public Real invCosine() {
        return IndeterminateReal.OUT_OF_DOMAIN;
    }
    
    public Real invTangent() {
        if (isPositive()) {
            return RealConstant.PI.evaluate().divideBy(Real.TWO);
        } else {
            return RealConstant.PI.evaluate().divideBy(Real.TWO).negate();
        }
    }
    
    public Real invSecant() {
        return RealConstant.PI.evaluate().divideBy(Real.TWO);
    }
    
    public Real invCosecant() {
        return Real.ZERO;
    }
    
    public Real invCotangent() {
        if (isPositive()) {
            return Real.ZERO;
        } else {
            return RealConstant.PI.evaluate();
        }
    }
    
    public Real hyperSine() {
        return this;
    }
    
    public Real hyperCosine() {
        return InfiniteReal.POSITIVE_INFINITY;
    }
    
    public Real hyperTangent() {
        if (isPositive()) {
            return Real.ONE;
        } else {
            return Real.NEGATIVE_ONE;
        }
    }
    
    public Real hyperSecant() {
        return Real.ZERO;
    }
    
    public Real hyperCosecant() {
        return Real.ZERO;
    }
    
    public Real hyperCotangent() {
        if (isPositive()) {
            return Real.ONE;
        } else {
            return Real.NEGATIVE_ONE;
        }
    }
    
    public Real invHyperSine() {
        return this;
    }
    
    public Real invHyperCosine() {
        if (isPositive()) {
            return this;
        } else {
            return IndeterminateReal.OUT_OF_DOMAIN;
        }
    }
    
    public Real invHyperTangent() {
        return IndeterminateReal.OUT_OF_DOMAIN;
    }
    
    public Real invHyperSecant() {
        if (isPositive()) {
            return Real.ZERO;
        } else {
            return IndeterminateReal.OUT_OF_DOMAIN;
        }
    }
    
    public Real invHyperCosecant() {
        return Real.ZERO;
    }
    
    public Real invHyperCotangent() {
        return Real.ZERO;
    }
    
    public Real factorial() {
        if (isPositive()) {
            return this;
        } else {
            return IndeterminateReal.UNKNOWN_SIGN;
        }
    }
    
    public Real exp() {
        if (isPositive()) {
            return this;
        } else {
            return Real.ZERO;
        }
    }
    
    public Real ln() {
        if (isPositive()) {
            return RealConstant.E.evaluate();
        } else {
            return IndeterminateReal.OUT_OF_DOMAIN;
        }
    }
    
    public Real sqrt() {
        if (isPositive()) {
            return this;
        } else {
            return IndeterminateReal.OUT_OF_DOMAIN;
        }
    }
    
    public Real power(Real exponent) {
        if (exponent.isIndeterminate()) {
            return IndeterminateReal.INDETERMINATE_OPERATOR;
        } else if (exponent.isInfinite()) {
            // inf ^ inf = inf
            // -inf ^ inf = undef (complex - out of domain)
            // inf ^ -inf = 1 / (inf ^ inf) = 1 / inf = 0
            // -inf ^ -inf = undef (complex - out of domain)
            if (isPositive()) {
                if (exponent.isPositive()) {
                    return POSITIVE_INFINITY;
                } else {
                    return Real.ZERO;
                }
            } else {
                return IndeterminateReal.OUT_OF_DOMAIN;
            }
        } else {
            if (exponent.equals(Real.ZERO)) {
                return Real.ONE;
            } else if (exponent.isPositive()) {
                // inf ^ pos = inf
                // -inf ^ pos.even = inf
                // -inf ^ pos.odd = -inf
                // -inf ^ pos.irrational = undef (complex - out of domain)
                if (isPositive()) {
                    return POSITIVE_INFINITY;
                } else if (exponent.isInteger()) {
                    if (exponent.modulo(Real.TWO).equals(Real.ZERO)) {
                        return POSITIVE_INFINITY;
                    } else {
                        return NEGATIVE_INFINITY;
                    }
                } else {
                    return IndeterminateReal.OUT_OF_DOMAIN;
                }
            } else {
                // inf ^ neg = 0
                // -inf ^ neg.even = 1 / (-inf ^ pos.even) = 1 / inf = 0
                // -inf ^ neg.odd = 1 / (-inf ^ pos.odd) = 1 / -inf = 0
                // -inf ^ neg.irrational = undef (complex - out of domain)
                if (exponent.isInteger()) {
                    return Real.ZERO;
                } else {
                    return IndeterminateReal.OUT_OF_DOMAIN;
                }
            }
        }
    }
}
